﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
using System.Diagnostics;
using Engine.Sgml.Merging;
using Engine.Sgml.Matching;
using Engine.Sgml.Schema;

namespace Engine.Sgml.Assembling {
    [DebuggerDisplay("Left: {leftBlocks.Count}, Right: {rightBlocks.Count}")]
    internal sealed class BuildingBlocks {
        private IList<InconsistentBlock> leftBlocks;
        private IList<InconsistentBlock> rightBlocks;
        private InconsistentBlock parentBlock;
        private int elementIndex;

        public BuildingBlocks() {
            leftBlocks = new List<InconsistentBlock>();
            rightBlocks = new List<InconsistentBlock>();
            }

        public bool HasElements {
            get { return leftBlocks.Count > 0 || rightBlocks.Count > 0; }
            }

        public void Finalize(Match match) {
            Debug.Assert(match != null);
            Correlate(rightBlocks.FirstOrDefault(b => object.ReferenceEquals(b.Ancestor, match.Ancestor)), match);
            leftBlocks.Clear();
            rightBlocks.Clear();
            parentBlock = null;
            elementIndex = -1;
            }

        public void Initialize(Match match, IList<InconsistentBlock> blocks) {
            Debug.Assert(match != null);
            Debug.Assert(blocks != null);
            leftBlocks.Clear();
            rightBlocks.Clear();
            if (blocks.Count == 0)
                return;
            int matchPrevIndex = match.StartIndex - 1;
            int matchNextIndex = match.NextIndex;
            InconsistentBlock leftBlock = null;
            InconsistentBlock rightBlock = null;

            for (int i = blocks.Count - 1; i >= 0; i--) {
                if (object.ReferenceEquals(match.Ancestor, blocks[i].Ancestor)) {
                    if (matchPrevIndex < blocks[i].StartIndex && blocks[i].StartIndex < matchNextIndex) {
                        rightBlocks.Add(blocks[i]);
                        Debug.Fail("Unexpected inconsistent block was found.");
                        continue;
                        }
                    if (blocks[i].StartIndex <= matchPrevIndex && matchNextIndex <= blocks[i].EndIndex) {
                        Debug.Fail("Unexpected inconsistent block was found.");
                        }
                    if (leftBlock == null) {
                        if (blocks[i].EndIndex == matchPrevIndex) {
                            leftBlock = blocks[i];
                            leftBlocks.Add(leftBlock);
                            continue;
                            }
                        }
                    if (rightBlock == null) {
                        if (blocks[i].StartIndex == matchNextIndex) {
                            rightBlock = blocks[i];
                            rightBlocks.Add(rightBlock);
                            continue;
                            }
                        }
                    else
                        break;
                    }
                else if (object.ReferenceEquals(match.Ancestor.Parent, blocks[i].Ancestor)) {
                    InconsistentBlock block = blocks[i];
                    for (int j = 0; j < block.Count; j++) {
                        if (object.ReferenceEquals(block[j], match.Ancestor) && block.Count > 1) {
                            Debug.Assert(parentBlock == null);
                            this.parentBlock = block;
                            this.elementIndex = j;
                            leftBlocks.Add(block);
                            break;
                            }
                        }
                    }
                }
            SElement ancestor = match.Ancestor;
            if (matchPrevIndex == -1 || (leftBlock != null && leftBlock.StartIndex == 0)) {
                SElement parent = match.Ancestor.Parent;
                while (parent != null) {
                    int startIndex = ancestor.OrderIndex - 1;
                    int endIndex = ancestor.OrderIndex + 1;
                    leftBlock = null;
                    for (int i = blocks.Count - 1; i >= 0; i--) {
                        if (object.ReferenceEquals(blocks[i].Ancestor, parent)) {
                            if (blocks[i].EndIndex == startIndex) {
                                leftBlock = blocks[i];
                                leftBlocks.Add(leftBlock);
                                break;
                                }
                            }
                        }
                    if (ancestor.OrderIndex == 0 || (leftBlock != null && leftBlock.StartIndex == 0)) {
                        ancestor = parent;
                        parent = parent.Parent;
                        }
                    else
                        break;
                    }
                }
            ancestor = match.Ancestor;
            if ((matchNextIndex == 0 ? ancestor.Nodes.Count == 0 : matchNextIndex == ancestor.Nodes.Count - 1) ||
                (rightBlock != null && rightBlock.EndIndex == ancestor.Nodes.Count - 1)) {
                SElement parent = match.Ancestor.Parent;
                while (parent != null) {
                    int startIndex = ancestor.OrderIndex - 1;
                    int endIndex = ancestor.OrderIndex + 1;
                    rightBlock = null;
                    for (int i = 0; i < blocks.Count; i++) {
                        if (object.ReferenceEquals(blocks[i].Ancestor, parent)) {
                            if (blocks[i].StartIndex == endIndex) {
                                rightBlock = blocks[i];
                                rightBlocks.Add(rightBlock);
                                break;
                                }
                            }
                        }
                    if (ancestor.OrderIndex == parent.Nodes.Count - 1 || (rightBlock != null && rightBlock.EndIndex == parent.Nodes.Count - 1)) {
                        ancestor = parent;
                        parent = parent.Parent;
                        }
                    else
                        break;
                    }
                }
            }

        private static void Correlate(InconsistentBlock block, Match match) {
            // Correlate first right inconsistent block and first match object
            Debug.Assert(match != null);
            if (block == null)
                return;
            if (block.StartIndex == match.StartIndex || match.EndIndex == block.StartIndex) {
                List<SElement> elements = new List<SElement>();
                elements.AddRange(block);
                block.Clear();
                for (int i = 0; i < elements.Count; i++) {
                    block.Add(elements[i]);
                    }
                }
            }
        }
    }
